/*
 *  FAT general support
 *
 *  Copyright(C) 2001
 *  Camilo Alejandro Arboleda
 *
 *  The contents of this file are distributed under the GNU General
 *  Public License version 2.
 *
 *  As a special exception, when this code is included in the RTEMS
 *  operating system, linking other files with RTEMS objects including
 *  this code does not cause the resulting executable application to
 *  be covered by the GNU General Public License. This exception does
 *  not however invalidate any other reasons why the executable file might
 *  be covered by the GNU General Public License.
 */

#include <sys/types.h>         /* for mkdir */
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <dirent.h>
#include <rtems.h>
#include <rtems/libio.h>
#include "fat.h"
#include "mtd.h"

extern rtems_filesystem_location_info_t rtems_filesystem_current;
extern rtems_filesystem_location_info_t rtems_filesystem_root;
extern int rtems_libio_is_file_open(void  *node_access);
extern void strlwr(char *);

#ifndef set_errno_and_return_minus_one
#define set_errno_and_return_minus_one( _error ) \
  do { errno = (_error); return -1; } while(0)
#endif

#define rtems_filesystem_is_separator( _ch ) \
   ( ((_ch) == '/') || ((_ch) == '\\') || ((_ch) == '\0'))

#define fat_is_not_valid_char( c ) \
   (((c < 'A') || (c > 'Z')) && ((c < 'a') || (c > 'z')) && ((c < '0') || (c >'9')) && (c!='.'))

#define fat_is_valid_char( c ) \
   (((c >= 'A') && (c =< 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <='9')) || (c=='.'))

#define upchar( c ) ( ((c >= 'a') && (c <= 'z')) ? (c-0x20):(c))
#define lowchar( c ) ( ((c >= 'A') && (c <= 'Z')) ? (c+0x20):(c))
                                            
#define SECTORSIZE 512

extern FAT_jnode_t *fat_root_dir;
extern FAT_jnode_t *fat_tail;

/* Adds a node to open nodes chain
 */
void
add_node(FAT_jnode_t *node)
{
   fat_tail->next = node;
   node->next = NULL;
   node->back = fat_tail;
   if (node->parent)
      node->parent->reference++;
   fat_tail = node;
}

/* Removes a node from open nodes chain
 */
void
del_node(FAT_jnode_t *node)
{
   FAT_jnode_t *index;
   FAT_jnode_t *parent = node->parent;
   index = fat_root_dir;

   if (node->reference > 0) return;

   while (index->next) {
      if (index->next == node) {
	 index->next = node->next;
	 if (index->next) {
            index->next->back = index;
	 } else {
            fat_tail = index;
	 }
	 free(node);
         return;
      }
      index = index->next;
   }
   if (parent) {
      parent->reference--;
      if (parent->reference <= 0) {
         del_node(parent);
      }
   }
}


/* Reads a block from FAT area
 */
void
read_fat_block(int cluster, fat_geom_t *geom)
{
   unsigned char buffer[SECTORSIZE * 2];
   unsigned offset2;
   int i;
   int fd = geom->fd;

   geom->fat_cache.offset = cluster;
   switch (geom->type) {
   case 0x04:
   case 0x06: /* FAT 16 */
      if (geom->fat_cache.entries == NULL) {
         geom->fat_cache.entries = calloc(256,sizeof(unsigned short));
         if (!geom->fat_cache.entries)
            exit(1); /* Error */
         geom->fat_cache.size = 256;
      }
      offset2 = geom->bytes_per_sect + (cluster * 2);
      lseek( fd, offset2, SEEK_SET );
      read( fd, buffer, geom->bytes_per_sect );
      for (i = 0;i < geom->fat_cache.size; i++)
         geom->fat_cache.entries[i] = * (unsigned short*)(buffer + i*2);
      break;
   case 0x01: /* FAT 12 */
      if (geom->fat_cache.entries == NULL) {
         geom->fat_cache.size = geom->sectors_per_fat * SECTORSIZE;
         geom->fat_cache.entries = calloc(geom->fat_cache.size,sizeof(unsigned char));
         if (!geom->fat_cache.entries)
            exit(1); /* Error */
         lseek(fd, SECTORSIZE, SEEK_SET);
         read(fd,geom->fat_cache.entries,geom->fat_cache.size);
      }
      break;
   default:
      break;
   }
}

/* Search one free cluster in disk
 */
unsigned
search_free_cluster(fat_geom_t *geom)
{
   fat_block_t *fat_block = &(geom->fat_cache);
   unsigned o2,t1, t2, cluster;

   /* FAT 12 */
   if (geom->type == 0x01) {
      for (cluster = 2; cluster < geom->fat_entries ; cluster++) {
         o2 = (cluster / 2) * 3;
         t1 = *(unsigned *)( ((char *)fat_block->entries) + o2) & 0xfff;
         t2 = (*(unsigned *)( ((char *)fat_block->entries) + o2) & 0xfff000) >> 12;
         if (t1 == 0)
            return cluster;
         cluster++;
         if (t2 == 0)
            return cluster;
      } /* end for */
      return -1;
   }
   return -1;
}

/*
 */
void
save_fat( fat_geom_t *geom)
{
   fat_block_t *fat_block = &(geom->fat_cache);
   unsigned offset = 0;
   unsigned offset2 = 0;
   unsigned fat_size;
   unsigned mask = 1;
   int fd = geom->fd;

   fat_size = SECTORSIZE * geom->sectors_per_fat;
   if (geom->type == 0x01) {
      offset = SECTORSIZE;
      while (mask) {
         if (fat_block->changed & mask ) {
            /* Write first fat */
            lseek(fd, offset, SEEK_SET);
            write(fd, ((char *)fat_block->entries) + offset2, SECTORSIZE);
            if (geom->total_FATs > 1) {
               /* Write second fat */
               lseek(fd, offset+fat_size, SEEK_SET);
               write(fd, ((char *)fat_block->entries) + offset2, SECTORSIZE);
            }
         }
         offset += SECTORSIZE;
         offset2 += SECTORSIZE;
         fat_block->changed &= ~mask; /* set as no changed */
         mask <<= 1; /* Next sector */
      }
   } else {
      if (!fat_block->changed) return;
      offset = geom->bytes_per_sect + (fat_block->offset * 2);
      lseek(fd, offset, SEEK_SET);
      write(fd, fat_block->entries, 256);
      if (geom->total_FATs > 1) {
         lseek(fd, offset+fat_size, SEEK_SET);
         write(fd, fat_block->entries, 256);
      }
      fat_block->changed = 0;
   }
};

/* Changes the value of fat entry "cluster" with "new_value"
 */
void
update_cluster(unsigned cluster, unsigned new_value, fat_geom_t *geom)
{
   fat_block_t *fat_block = &(geom->fat_cache);
   unsigned o2,t1, bit1,bit2;
   unsigned tmp_offset;

   if (geom->type == 0x01) {
      /* FAT 12 */
      o2 = (cluster / 2) * 3;
      t1 = *(unsigned *)( ((char *)fat_block->entries) + o2);
      if (cluster & 0x01) {
         new_value &= 0x00000fff;
         new_value <<= 12;
         t1 &= 0xff000fff;
         t1 |= new_value;
      } else {
         new_value &= 0x00000fff;
         t1 &= 0xfffff000;
         t1 |= new_value;
      }
      *(unsigned *)( ((char *)fat_block->entries) + o2) = t1;
      bit1 = 1 << (o2 / SECTORSIZE);
      bit2 = 1 << ( (o2 + 3) / SECTORSIZE);
      fat_block->changed |= (bit1 | bit2);
   } else {
      /* FAT 16 */
      if ( fat_block->offset <= cluster &&
        (fat_block->offset + 256) > cluster ) {
         fat_block->entries[cluster-fat_block->offset] = new_value;
      } else {
         if (fat_block->changed)
            save_fat( geom);
         tmp_offset = cluster - cluster % 256;
         read_fat_block(tmp_offset,geom);
         fat_block->entries[cluster-fat_block->offset] = new_value;
      }
      fat_block->changed = 1;
   }
}

/* Add a new cluster to a chain
 */
void
add_cluster(unsigned last_cluster, unsigned new_cluster, fat_geom_t *geom)
{
   /* Make last_cluster point new cluster */
   update_cluster(last_cluster,new_cluster, geom);

   /* Make new_cluster last cluster in chain */
   update_cluster(new_cluster,0xfff, geom);
}

/* Search the next cluster in a cluster chain
 */
unsigned
search_next_cluster(int cluster, fat_geom_t *geom)
{
   unsigned tmp_offset;
   unsigned o2;
   unsigned t1;
   fat_block_t *fat_block = &(geom->fat_cache);

   /* FAT 12 */
   if (geom->type == 0x01) {
      o2 = (cluster / 2) * 3;
      if (cluster & 0x01)
         t1 = (*(unsigned *)( ((char *)fat_block->entries) + o2) & 0xfff000) >> 12;
      else
         t1 = *(unsigned *)( ((char *)fat_block->entries) + o2) & 0xfff;
      if (t1 > 0xff0) t1 |= 0xf000;
      return t1;
   }

   /* FAT 16 */
   if ( fat_block->offset <= cluster &&
        (fat_block->offset + 256) > cluster ) {
      return fat_block->entries[cluster-fat_block->offset];
   }
   tmp_offset = cluster - cluster % 256;
   read_fat_block(tmp_offset,geom);
   return fat_block->entries[cluster-fat_block->offset];
}

/* Find the cluster correspondig to current cluster
 */
int
find_cluster(FAT_jnode_t *node, int offset, unsigned *cluster)
{
   int new_cluster = node->current_cluster;
   int offset2 = node->cache_offset - node->cache_offset % node->geometry->cluster_size;

   *cluster = node->current_cluster;
   offset &= 0xfffffe00;

   if ( (offset < node->cache_offset) || (node->cache_offset < 0) ) {
      /* Previous cluster or no cluster at all */
      *cluster = node->st_ino;
      offset2 = 0;
   }

   while ( (offset2+node->geometry->cluster_size) <= offset ) {
      new_cluster = search_next_cluster(*cluster, node->geometry);
      if (new_cluster > 0xfff0) return -1; /*  */
      *cluster = new_cluster;
      offset2 += node->geometry->cluster_size;
   }
   return *cluster;
}

/* Reads a block from disk
 */
int
read_root_block(FAT_jnode_t *node, fat_geom_t *geom, int offset)
{
   int disk_offset;
   int fd = geom->fd;

   /* check block alignment */
   if ( (offset & 0x1ff) != 0 ) return -1;

   disk_offset = geom->root_dir_offset + offset;
   if (disk_offset >= geom->data_offset) return -1;

   lseek(fd,disk_offset,SEEK_SET);
   read(fd,node->cache,SECTORSIZE);

   node->current_cluster = 0;
   node->cache_offset  = offset - offset % SECTORSIZE;

   return 0;
}

/* Reads a block from disk
 */
int
read_block(FAT_jnode_t *node, fat_geom_t *geom, int offset)
{
   int cluster = node->current_cluster;
   int offset2 = node->cache_offset - node->cache_offset % geom->cluster_size;
   int disk_offset;
   int fd = geom->fd;

   /* check block alignment */
   if ( (offset & 0x1ff) != 0 ) return -1;

   if ( (offset < node->cache_offset) || (node->cache_offset < 0) ) {
      /* Previous cluster or no cluster at all */
      cluster = node->st_ino;
      offset2 = 0;
   }

   while ( (offset2+geom->cluster_size) <= offset ) {
      cluster = search_next_cluster(cluster, geom);
/*      if ( (cluster >= 0xfff8) && (cluster <= 0xffff) ) return cluster; */
      if (cluster > 0xfff0) return -1; /* Invalid off-set */
      offset2 += geom->cluster_size;
   }

/*    if (cluster == node->current_cluster) return 0; */

   disk_offset = cluster * geom->cluster_size +
                 geom->first_cluster_offset +
                 offset % geom->cluster_size;

   lseek(fd,disk_offset,SEEK_SET);
   read(fd,node->cache,SECTORSIZE);

   node->current_cluster = cluster;
   node->cache_offset  = offset - offset % SECTORSIZE;

   return 0;
}

/*-------------------------------------------------------------------
 * Not optimized fat read. It reads byte by byte and does not handle
 * errors
 */
int
fat_read (FAT_jnode_t *node, char *buffer, int count)
{
   int byte_offset = node->offset & 0x1ff;
   int offset = node->offset & 0xfffffe00;
   int moved = 0;
   int bytes, buffer_offset = 0;
   int status;

   if ( (node->type == FAT_FILE) && (count + node->offset > node->size) )
      count = node->size - node->offset;

   while ( count > 0 ) {
      if ( (node->type == FAT_FILE) && (node->offset >= node->size) ) {
         return moved;
      }
      /* Read requested sector if needed */
      if ( (node->offset >= node->cache_offset + SECTORSIZE) ||
           (node->offset < node->cache_offset) ||
           (node->cache_offset < 0)) {
         status = node->parent ? 
            read_block(node, node->geometry, offset):
            read_root_block(node, node->geometry, offset);
         if (status) {
            return -1;
         }
         offset += SECTORSIZE;
      }
      /* How many bytes are going to be read from the current sector */
      bytes = (count < (SECTORSIZE - byte_offset)) ? count: (SECTORSIZE - byte_offset);
      /* Transfer them to cache */
      memcpy (buffer+buffer_offset, node->cache + byte_offset, bytes);
      buffer_offset += bytes;
      byte_offset   += bytes;
      byte_offset   &= 0x1ff;
      count         -= bytes;
      node->offset  += bytes;
      moved         += bytes;
   } /* end while */
   return moved;
}


/* Writes a block from disk
 */
int
write_root_block(FAT_jnode_t *node, fat_geom_t *geom)
{
   int disk_offset;
   int offset;
   int fd = geom->fd;

   /* check block alignment */
   if (node->cache_offset < 0) node->cache_offset = 0;
   offset = node->cache_offset - node->cache_offset % SECTORSIZE;

   disk_offset = geom->root_dir_offset + offset;
   if (disk_offset >= geom->data_offset) return -1;

   lseek(fd,disk_offset,SEEK_SET);
   write(fd,node->cache,SECTORSIZE);

   return 0;
}
/*-------------------------------------------------------------------
 * Writes block to disk
 */
int
write_block(FAT_jnode_t *node, fat_geom_t *geom)
{

   int cluster = node->current_cluster;
   int disk_offset;
   int fd = geom->fd;
   int offset;

   /* check block alignment */
   if (node->cache_offset < 0) node->cache_offset = 0;
   offset = node->cache_offset - node->cache_offset % SECTORSIZE;


   disk_offset = cluster * geom->cluster_size +
                 geom->first_cluster_offset +
                 offset % geom->cluster_size;

   lseek(fd,disk_offset,SEEK_SET);
   write(fd,node->cache,SECTORSIZE);

   return 0;
}

/*-------------------------------------------------------------------
 * Not optimized fat write. It reads byte by byte and does not handle
 * errors
 */
int
fat_write (FAT_jnode_t *node, const char *buffer, int count)
{
   int moved = 0;
   int status, read_sector, cluster = 0;
   int byte_offset, sector_offset, bytes;


   while (count > 0) {
      byte_offset = node->offset & 0x1ff;
      sector_offset = node->offset & 0xfffffe00;
      read_sector = 1;

      bytes = count > SECTORSIZE ? SECTORSIZE : count; /* Write less than one sector */
      bytes = (bytes + byte_offset) > SECTORSIZE ? SECTORSIZE - byte_offset: bytes;

      if ( (node->offset + bytes > node->size) && (node->type == FAT_FILE) ) {
         if (node->size == 0) {
            /* New file */
            /* printf("Archivo nuevo\n"); */
            node->cache_offset = 0;
            node->current_cluster = node->st_ino;
            read_sector = 0;
         } else if ( (node->size % SECTORSIZE) !=0 ) {
            /* Room in the sector */
            /* Do nothing */
            /* printf("Hay espacio en el sector\n"); */
         } else if ( (node->size % node->geometry->cluster_size) != 0 ) {
            /* Room in the cluster */
            /* printf("Hay espacio en el cluster\n"); */
            if (node->cache_offset != sector_offset) {
               find_cluster(node,sector_offset,&node->current_cluster);
            }
            node->cache_offset = sector_offset;
            byte_offset = 0;
            /* No need to read the sector because it is empty */
            read_sector = 0;
         } else {
            /* File must grow */
            /* printf("El archivo debe crecer\n"); */
            if (find_cluster(node,sector_offset,&node->current_cluster) < 0) {

               rtems_semaphore_obtain( node->geometry->mutex_id,
                                       RTEMS_WAIT,RTEMS_NO_TIMEOUT );

               cluster = search_free_cluster(node->geometry);
               /* printf("cluster actual: %d\n",node->current_cluster);
               printf("nuevo cluster: %d\n",cluster); */

               if (cluster < 2) { /* No room in disk */
                  rtems_semaphore_release( node->geometry->mutex_id );
                  return moved;
               }

               add_cluster(node->current_cluster, cluster, node->geometry);

               rtems_semaphore_release( node->geometry->mutex_id );
            }
            read_sector = 0; /* New cluster is empty */
            node->cache_offset = sector_offset;
            node->current_cluster = cluster;
            byte_offset = 0;
         }
         node->size += bytes;
      } else {
      }
      if (read_sector) {
         status = node->parent ?
            read_block(node, node->geometry, sector_offset):
            read_root_block(node, node->geometry, sector_offset);
      }
      memcpy(node->cache+byte_offset,buffer+moved,bytes);
      status = node->parent ?
         write_block(node, node->geometry):
         write_root_block(node, node->geometry);
      count        -= bytes;
      moved        += bytes;
      node->offset += bytes;

   } /* end while */
   return moved;
}

/*-------------------------------------------------------------------
 * After any change (write or truncate) in a FAT file, directory
 * entry must be updated.
 */
int
update_dir_entry(FAT_jnode_t *node)
{
   FAT_jnode_t *parent = node->parent;
   unsigned char buffer[32];
   int j, k;
   rtems_time_of_day tod;
   unsigned short date;
   unsigned short time;

   if (node->type != FAT_FILE)
      return 0;
   if (parent) {
      memset(buffer,0,32);
      memset(buffer,0x20,11);
      rtems_clock_get(RTEMS_CLOCK_GET_TOD, &tod);
      for (j=0, k=0; node->name[j] != 0; j++) {
         if (node->name[j] == '.') {
            k = 8;
            continue;
         }                   
         buffer[k++] = upchar(node->name[j]);
      };
      buffer[11]  = 0x20; /* Normal file */
      time = (tod.hour << 11) + (tod.minute << 5) + (tod.second / 2);
      date = ((tod.year - 1980) << 9) + (tod.month << 5) + tod.day;
      *(unsigned short *)(buffer+22) = time;
      *(unsigned short *)(buffer+24) = date;
      *(unsigned short *)(buffer+26) = node->st_ino;
      *(unsigned       *)(buffer+28) = node->size;
      parent->offset = node->dir_offset;
      fat_write(parent, buffer, 32);
   } else {
   }

   return 0;
}

/*-------------------------------------------------------------------
 *
 */
FAT_jnode_t *
FAT_find_match_in_dir(const char *token, FAT_jnode_t *parent)
{
   FAT_jnode_t *index;
   unsigned char buffer[sizeof(struct dirent)];

   index = fat_root_dir;

   while (index) {
      if (index->parent == parent) {
	 if (!strcmp(token,index->name)) {
	    return index;
	 }
      }
      index = index->next;
   }

   if (!index) {
      /* If the node does not exist, find it
       * and create a new one
       */
      parent->offset = 0;
      index = (FAT_jnode_t *)calloc(1, sizeof(FAT_jnode_t));
      if (!index)
         return NULL;
      while (fat_dir_read( parent, buffer,sizeof(struct dirent), index ) > 0) {
         if (strcmp(token, index->name) == 0) {
            /* printf("%s %s\n",token,index->name); */
            index->cache_offset = -1;
            index->offset = 0;
            index->current_cluster = index->st_ino;
            add_node(index);
            return index;
         }
      };
      free(index);
      return NULL;
   }
   return index;
}


inline int
FAT_Set_handlers( rtems_filesystem_location_info_t *loc )
{
  FAT_jnode_t    *node = loc->node_access;

  switch( node->type ) {
    case FAT_DIRECTORY:
      loc->handlers = &FAT_directory_handlers;
      break;
    case FAT_FILE:
      loc->handlers = &FAT_file_handlers;
      break;
  default:
     loc->handlers = NULL;
     return -1;
     break;
  }

  return 0;
}

FAT_token_types
FAT_get_token( const char *path, char *token, int *token_len )
{
  register int i = 0;
  FAT_token_types  type = FAT_NAME;
  register char c;

  /* 
   *  Copy a name into token.  (Remember NULL is a token.)
   */
  c = path[i];
  while ( (c != '/') && (c != '\0') && (i <= FAT_NAME_MAX+1) ) {

     token[i] = c;

     if ( i > (FAT_NAME_MAX+1) )
	return FAT_INVALID_TOKEN;

     if ( fat_is_not_valid_char(c) )
	type = FAT_INVALID_TOKEN;

     c = path [++i];
  }

  /*
   *  Copy a seperator into token.
   */

  if ( i == 0 ) {
     token[i] = c;

     if ( token[i] != '\0' ) {
	i++;
	type = FAT_CURRENT_DIR;
     } else {
	type = FAT_NO_MORE_PATH;
     }
  } else if (token[ i-1 ] != '\0') {
     token[i] = '\0';
  }

  /*
   *  Set token_len to the number of characters copied.
   */

  *token_len = i;

  /*
   *  If we copied something that was not a seperator see if
   *  it was a special name.
   */

  if ( type == FAT_NAME ) {
     if ( strcmp( token, "..") == 0 )
	type = FAT_UP_DIR;
     else if ( strcmp( token, "." ) == 0 )
	type = FAT_CURRENT_DIR;
  }

  return type;
}

/*
 *  FAT_evaluate_for_make
 *
 *  The following routine evaluate path for a new node to be created.
 *  pathloc is returned with a pointer to the parent of the new node.
 *  name is returned with a pointer to the first character in the 
 *  new node name.  The parent node is verified to be a directory.
 */

int
FAT_evaluate_for_make(path, pathloc, name)
   const char                         *path;       /* IN     */
   rtems_filesystem_location_info_t   *pathloc;    /* IN/OUT */
   const char                        **name;        /* OUT    */
{
  int                                 i = 0;
  int                                 len;
  FAT_token_types                     type;
  char                                token[ 14 ];
  rtems_filesystem_location_info_t    newloc;
  FAT_jnode_t                        *node;
  int                                 done = 0;
  int                                 result; 
  
  /*
   * This was filled in by the caller and is valid in the 
   * mount table.
   */
  node = pathloc->node_access;

  /*
   *  Evaluate all tokens until we are done or an error occurs.
   */

  while( !done ) {

    type = FAT_get_token( &path[i], token, &len );
    i +=  len;
    
    if ( !pathloc->node_access )
      set_errno_and_return_minus_one( ENOENT );

    node = pathloc->node_access;

    switch( type ) {

      case FAT_UP_DIR:

	/*
	 * Am I at the root of this mounted filesystem?
	 */

        if (pathloc->node_access == pathloc->mt_entry->mt_fs_root.node_access) {

           newloc = pathloc->mt_entry->mt_point_node;
           *pathloc = newloc;
           return (*pathloc->ops->evalformake)( &path[i-len], pathloc, name );

	} else {

          if ( !node->parent )
            set_errno_and_return_minus_one( ENOENT );

          node = node->parent;
	}

        pathloc->node_access = node;
        break;

      case FAT_NAME:

        node = pathloc->node_access;
        if ( !node )
          set_errno_and_return_minus_one( ENOTDIR ); 

        /*
         * Only a directory can be decended into.
	 */

        if ( node->type != FAT_DIRECTORY )
          set_errno_and_return_minus_one( ENOTDIR );

	/*
	 * Otherwise find the token name in the present location.
	 */

        strlwr(token);
        node = FAT_find_match_in_dir( token, node );

	/*
	 * If there is no node we have found the name of the node we 
         * wish to create.
	 */

        if ( ! node )
          done = TRUE;
        else
          pathloc->node_access = node;

        break;
 
      case FAT_NO_MORE_PATH:
        set_errno_and_return_minus_one( EEXIST );
        break;

      case FAT_INVALID_TOKEN:
        set_errno_and_return_minus_one( ENAMETOOLONG );
        break;

      case FAT_CURRENT_DIR:
        break;
    }
  }

  *name = &path[ i - len ];
   
  /*
   * We have evaluated the path as far as we can.
   * Verify there is not any invalid stuff at the end of the name. 
   */

  for( ; path[i] != '\0'; i++) {
    if ( !rtems_filesystem_is_separator( path[ i ] ) )
      set_errno_and_return_minus_one( ENOENT );
  }

  /* 
   * Verify we can execute and write to this directory.
   */

  node = pathloc->node_access;
  result = FAT_Set_handlers( pathloc );

  /*
   * The returned node must be a directory
   */
  if ( node->type != FAT_DIRECTORY )
    set_errno_and_return_minus_one( ENOTDIR );

  node->reference++;

  return 0;
}


/*
 *  FAT_eval_path
 *
 *  The following routine evaluate path for a node that wishes to be
 *  accessed with mode.  pathloc is returned with a pointer to the
 *  node to be accessed.
 */

int
FAT_eval_path(pathname,flags,pathloc)
  const char                        *pathname;     /* IN     */
  int                                flags;        /* IN     */
  rtems_filesystem_location_info_t  *pathloc;      /* IN/OUT */
{
   int                                 i = 0;
   int                                 len;
   FAT_token_types                     type = FAT_CURRENT_DIR;
   char                                token[ 14 ];
   rtems_filesystem_location_info_t    newloc;
   FAT_jnode_t                         *node;
   int                                 result;


  /*
   *  This was filled in by the caller and is valid in the 
   *  mount table.
   */

  node = pathloc->node_access;

  /*
   *  Evaluate all tokens until we are done or an error occurs.
   */

  while( (type != FAT_NO_MORE_PATH) && (type != FAT_INVALID_TOKEN) ) {

    type = FAT_get_token( &pathname[i], token, &len );
    i +=  len;
    
    if ( !pathloc->node_access )
      set_errno_and_return_minus_one( ENOENT );

    node = pathloc->node_access;

    switch( type ) {
      case FAT_UP_DIR:

	/*
	 *  Am I at the root of this mounted filesystem?
	 */

        if (pathloc->node_access == pathloc->mt_entry->mt_fs_root.node_access) {

           newloc = pathloc->mt_entry->mt_point_node;
           *pathloc = newloc;
           return (*pathloc->ops->evalpath)(&(pathname[i-len]),flags,pathloc);

	} else {

          if ( !node->parent )
            set_errno_and_return_minus_one( ENOENT );

          node = node->parent;
          pathloc->node_access = node;

	}

        pathloc->node_access = node;
        break;

      case FAT_NAME:
       /*
        *  Only a directory can be decended into.
        */

       if ( node->type != FAT_DIRECTORY )
          set_errno_and_return_minus_one( ENOTDIR );

	/*
	 *  Otherwise find the token name in the present location.
	 */

        strlwr(token);
        node = FAT_find_match_in_dir( token, node );
        if ( !node )
          set_errno_and_return_minus_one( ENOENT );

	/*
	 *  Set the node access to the point we have found.
	 */

        pathloc->node_access = node;
        break;

      case FAT_NO_MORE_PATH:
      case FAT_CURRENT_DIR:
        break;

      case FAT_INVALID_TOKEN:
        set_errno_and_return_minus_one( ENAMETOOLONG );
        break;

    }
  }

  result = FAT_Set_handlers( pathloc );
  node->reference++;

  return result;
}

/* FAT_node_type
 * Return type of node. It might be FAT_FILE or FAT_DIR
 */
rtems_filesystem_node_types_t
FAT_node_type( rtems_filesystem_location_info_t *pathloc )
{
  FAT_jnode_t *node;

  node = pathloc->node_access;
  return node->type;
}

/*
 *  FAT_mknod
 *
 *  Routine to create a node in the FAT file system.
 */
int
FAT_mknod(token, mode, dev, pathloc)
  const char                        *token;      /* IN */
  mode_t                             mode;       /* IN */
  dev_t                              dev;        /* IN */
  rtems_filesystem_location_info_t  *pathloc;     /* IN/OUT */
{
   FAT_jnode_t      *new_node;
   FAT_jnode_t      *node = pathloc->node_access;
   /* rtems_filesystem_location_info_t  tmp_pathloc; */
   int               result, type;
   char              new_name[ 14 ];
  
   FAT_get_token( token, new_name, &result );

   /*
    *  Figure out what type of FAT node this is.
    */

   if ( S_ISDIR(mode) ) {
      set_errno_and_return_minus_one( EINVAL );
   } else if ( S_ISREG(mode) ) {
      type = FAT_FILE;
   } else  {
      set_errno_and_return_minus_one( EINVAL );
   }

   /*
    *  Allocate and fill in a FAT jnode
    */

   new_node = calloc(1, sizeof (FAT_jnode_t));
   if (!new_node)
      set_errno_and_return_minus_one( EINVAL );

   new_node->geometry = node->geometry;
   rtems_semaphore_obtain(node->geometry->mutex_id,RTEMS_WAIT,RTEMS_NO_TIMEOUT);
   new_node->dir_offset = find_free_entry(node);
   if (new_node->dir_offset < 0) {
     free(new_node);
     set_errno_and_return_minus_one( EINVAL );
   }
   new_node->st_ino = search_free_cluster(node->geometry);
   if (new_node->st_ino < 0) {
      free(new_node);
      set_errno_and_return_minus_one( EINVAL );
   }

   new_node->parent = node;
   new_node->reference = 0;
   new_node->type = FAT_FILE;
   new_node->attrib = 0x20;
   new_node->size = 0;

   add_node(new_node);
   update_cluster(new_node->st_ino, 0xffff, node->geometry);
   strcpy(new_node->name, new_name);
   update_dir_entry(new_node);
   save_fat(node->geometry);

   rtems_semaphore_release(node->geometry->mutex_id);

   /* result = FAT_Set_handlers( new_node ); */

   return 0;
}


/*
 *  FAT_rmnod
 */

int
FAT_rmnod( rtems_filesystem_location_info_t *pathloc)
{
   FAT_jnode_t *node;
   fat_geom_t  *geom;
   int         cluster, next_cluster;

   node = (FAT_jnode_t *) pathloc->node_access;
   geom = node->geometry;

   /*
    * The file cannot be open to free.
    */

   if ( ( !rtems_libio_is_file_open( node ) ) && (node->type == FAT_FILE) ) {

      /*
       * Is rtems_filesystem_current this node?
       */

      if ( rtems_filesystem_current.node_access == pathloc->node_access )
         rtems_filesystem_current.node_access = NULL;

      rtems_semaphore_obtain(geom->mutex_id,RTEMS_WAIT,RTEMS_NO_TIMEOUT);
      cluster = node->st_ino;
      do {
         next_cluster = search_next_cluster(cluster, geom);
         update_cluster(cluster, 0x0000, geom);
         cluster = next_cluster;
      } while (cluster < 0xfff0);
      /* update_cluster(cluster, 0x0000, geom); */
      node->name[0] = 0xe5;

      node->reference--;
      update_dir_entry(node);
      if (node->reference <= 0)
         del_node(node);

      /* del_node(node); */
      save_fat(geom);

      rtems_semaphore_release(geom->mutex_id);
   } else {
      /* File is opened, so cannot be deleted
       */
   }

   return 0;
}

/*
 *  FAT_link.
 *  Actually it is a move function, because fat does not support links
 */
int
FAT_link(to_loc, parent_loc, token)
  rtems_filesystem_location_info_t  *to_loc;
  rtems_filesystem_location_info_t  *parent_loc;
  const char                        *token;
{
   FAT_jnode_t      *node, *new_parent;
   char              new_name[ 14 ];
   int               i, new_offset;

   node = (FAT_jnode_t *) to_loc->node_access;
   new_parent = (FAT_jnode_t *) parent_loc->node_access;

   /*
    * Remove any separators at the end of the string.
    */

   FAT_get_token( token, new_name, &i );

   rtems_semaphore_obtain( node->geometry->rw_mutex_id,
                           RTEMS_WAIT,RTEMS_NO_TIMEOUT );

   rtems_semaphore_obtain(node->geometry->mutex_id,RTEMS_WAIT,RTEMS_NO_TIMEOUT);
   new_offset = find_free_entry(new_parent);
   if (new_offset < 0) {
      rtems_semaphore_release(node->geometry->mutex_id);
      rtems_semaphore_release( node->geometry->rw_mutex_id );
      set_errno_and_return_minus_one( EINVAL );
   }

   /* Mark current entry as deleted */
   node->name[0] = 0xe5;
   update_dir_entry(node);

   /* Create new one */
   strcpy(node->name,new_name);
   node->parent->reference--;
   node->parent = new_parent;
   node->dir_offset = new_offset;
   node->parent->reference++;
   update_dir_entry(node);

   rtems_semaphore_release( node->geometry->rw_mutex_id );
   rtems_semaphore_release(node->geometry->mutex_id);

  return 0;
}
/*
 *  FAT_freenodinfo
 *
 */

int
FAT_freenodinfo(rtems_filesystem_location_info_t  *pathloc )
{
  FAT_jnode_t *node;

  node = pathloc->node_access;

  node->reference--;
  del_node(node);

  return 0;
}


